const {Service} = require("ee-core");
const Socket = require("ee-core/socket");
const {json, mapJson} = require("../utils");
const {Client} = require('ssh2');
const utf8 = require('utf8');
const Addon = require("ee-core/addon");
const connList = []
const _ = require('lodash')
const {statSync, createReadStream, existsSync, mkdirSync, createWriteStream} = require("fs");
const {join, dirname, relative} = require("path");
const {reject} = require("lodash");
let downloadLengthObj = {}
let totalLengthObj = {}

class SshService extends Service {

    constructor(ctx) {
        super(ctx);
    }

    async getItemById(args) {
        const {remote_ssh} = this.app['Seque'].models
        const data = await remote_ssh.findOne({
            where: {
                id: args.query.id
            }
        })
        return mapJson(data)
    }

    async createWindow(args) {
        const win = Addon.get('window');
        let data = win.create(args.uuid, {
            title: args.name + '-' + args.host,
            minWidth: 1000,
            minHeight: 700
        })
        data.on('close', () => {
            if (args.route === '/sftp') {
                args.uuid = args.uuid + '_sftp'
            }
            this.closeSSh(args)
        })
        const winURL =
            process.env.EE_SERVER_ENV === "local"
                ? "http://localhost:8090"
                : `file://${__dirname}/index.html`;
        await data.loadURL(winURL + "#" + `${args.route}?id=${args.id}`);
    }

    async getSsh(args) {
        let {page_size = 10, page = 1} = args.query
        page_size = Number(page_size)
        page = Number(page)
        const {remote_ssh} = this.app['Seque'].models
        let data = await remote_ssh.findAll({
            where: {
                del_flag: 0
            },
            order: [['create_time', 'desc']],
            limit: page_size,
            offset: (page - 1) * page_size
        })
        let total = await remote_ssh.count()
        return {code: 200, total, data}
    }

    async delSsh(args) {

        const {remote_ssh} = this.app['Seque'].models
        await remote_ssh.update({
            del_flag: 1
        }, {
            where: {
                id: args.id
            }
        })
    }

    async createOrUpdate(args) {
        console.log(args)
        return new Promise(async (resolve, reject) => {
            try {
                const {remote_ssh} = this.app['Seque'].models
                if (args.id) {
                    await remote_ssh.update(args, {
                        where: {
                            id: args.id
                        }
                    })
                    resolve()
                } else {
                    let data = await remote_ssh.findOrCreate({
                        where: {
                            name: args.name,
                        },
                        defaults: args
                    })
                    if (!data[1]) reject('创建失败：已存在该名称')
                    else resolve()
                }
            } catch (e) {
                reject(e.message)
            }

        })

    }

    async sftpGetTree(args) {
        async function readdir(sftp, path) {
            return new Promise((resolve, reject) => {
                sftp.readdir(path, function (err, list) {
                    if (err) {
                        reject(err)
                        return
                    }
                    for (let el of list) {
                        el.is_folder = el.attrs.isFile() ? 0 : 1
                        el.is_file = el.attrs.isFile() ? 1 : 0
                        el.isFIFO = el.attrs.isFIFO() ? 1 : 0
                        el.isSymbolicLink = el.attrs.isSymbolicLink() ? 1 : 0
                        el.isBlockDevice = el.attrs.isBlockDevice() ? 1 : 0
                        el.isSocket = el.attrs.isSocket() ? 1 : 0
                    }
                    resolve(list)
                });
            })
        }

        return new Promise(async (resolve, reject) => {
            try {
                const {password, username, host, uuid, path = '/'} = args
                let i = connList.findIndex(el => el.data.uuid === uuid + '_sftp')
                let Conn = null
                if (i < 0) {
                    Conn = new Client()
                    Conn.connect({
                        password,
                        username,
                        host,
                    });
                    Conn.on('ready', () => {
                        Conn.sftp(async (err, sftp) => {
                            connList.push({
                                data: {
                                    ...args,
                                    ...{
                                        uuid: args.uuid + '_sftp'
                                    }
                                },
                                conn: Conn,
                                sftp
                            })
                            if (err) {
                                console.error('SFTP Error:', err);
                                reject(err)
                                return;
                            }
                            let data = await readdir(sftp, path)
                            resolve(data)
                        });
                    });
                } else {
                    let sftp = connList[i].sftp
                    let data = await readdir(sftp, path)
                    resolve(data)
                }
            } catch (e) {
                reject(e)
            }

        })

    }

    async closeSSh(args) {
        try {
            console.log(args, connList)
            let index = connList.findIndex(el => el.uuid = args.uuid)
            connList[index].stream.end(); // 立即销毁流
            connList[index].stream.destroy(); // 立即销毁流
            connList[index].conn.end(); // 立即销毁流
            connList.splice(index, 1)
            return json({})
        } catch (e) {
            return json({coded: 500, msg: e.message})
        }

    }

    async sendData(args) {
        const {rows, cols, type, data, value} = args
        const {io} = Socket.getSocketServer();
        let i = connList.findIndex(el => el.data.uuid === data.uuid)
        if (i < 0) {
            const conn = new Client()
            conn.connect({
                password: data.password,
                username: data.username,
                host: data.host,
            });
            conn.on('ready', () => {
                conn.shell({term: 'xterm'}, async (err, stream) => {
                    if (err) {
                        console.error('SSH shell创建失败:', err);
                        return;
                    }
                    connList.push({
                        stream,
                        conn,
                        data,
                        cols,
                        rows
                    })
                    // 将 SSH 流的输入传输到伪终端
                    stream.on('data', (str) => {
                        const string = utf8.decode(str.toString('binary'))
                        io.emit(`term_data_${data.uuid}`, {
                            uuid: data.uuid,
                            value: string
                        })
                    });
                    stream.stderr.on('data', (str) => {
                        const string = utf8.decode(str.toString('binary'))
                        io.emit(`term_data_${data.uuid}`, {
                            uuid: data.uuid,
                            value: string
                        })
                    });

                    // 处理 SSH 流的关闭事件
                    stream.on('close', () => {
                        console.log('SSH流已关闭');
                        conn.end();
                    });
                });
            });
            conn.on('error', (err) => {
                console.error('SSH连接错误:', err);
            });
            conn.on('end', () => {
                console.log('SSH连接结束');
            });
            conn.on('close', () => {
                console.log('SSH连接关闭');
            });
        } else {
            connList[i].data = data
            connList[i].cols = cols
            connList[i].rows = rows
            let stream = connList[i].stream
            if (!stream) {
                connList[i].conn.on('ready', () => {
                    connList[i].conn.shell({term: 'xterm'}, async (err, stream) => {
                        if (err) {
                            console.error('SSH shell创建失败:', err);
                            return;
                        }
                        if (type === 'resize') {
                            stream.setWindow(rows, cols)
                        } else {
                            stream.write(value)
                        }
                    });
                });
            } else {
                if (type === 'resize') {
                    stream.setWindow(rows, cols)
                } else {
                    stream.write(value)
                }
            }

        }
    }


    uploadFileByFtp(args) {
        let {path, remote_path, create_time, uuid, sftp_uuid, name} = args
        const i = connList.findIndex(el => el.data.uuid === sftp_uuid + `_sftp`)
        path = path.replaceAll('//', '/')
        remote_path = remote_path + '/' + name
        remote_path = remote_path.replaceAll('//', '/')
        if (i < 0) return
        const {io} = Socket.getSocketServer();
        const sftp = connList[i].sftp

        const readStream = createReadStream(path);
        const writeStream = sftp.createWriteStream(remote_path, {
            flags: 'w',  // 覆盖写入文件
            mode: 0o755, // 设置文件权限
            encoding: 'utf8',
            autoClose: true
        });
        const startTime = new Date(create_time); // 记录开始时间
        const fileSize = statSync(path).size; // 获取文件大小
        let uploadedBytes = 0; // 已上传字节数
        console.log(`Starting upload of ${path}...`);
        console.log(sftp.fastPut)

        function onData(chunk) {
            uploadedBytes += chunk.length; // 累加已上传字节数
            const elapsedTime = (Date.now() - startTime) / 1000; // 计算上传的时间（秒）
            // 计算上传速度（字节/秒）
            const uploadSpeed = (uploadedBytes / elapsedTime) / (1024 * 1024);
            // 计算上传进度（百分比）
            const progress = (uploadedBytes / fileSize) * 100;
            io.emit(`uploadFileProgress_${uuid}`, {
                progress: progress.toFixed(2)
            })
            io.emit(`uploadFileSpeed_${uuid}`, {
                speed: `${Number(uploadSpeed.toFixed(2))} MB/s`
            })
        }

        const throttledHandleData = _.throttle(onData, 100);
        // 监听上传进度
        readStream.on('data', (chunk) => {
            throttledHandleData(chunk)
        });

        // 完成上传
        writeStream.on('close', () => {
            if (uploadedBytes >= fileSize) {
                io.emit(`uploadFileStatus_${uuid}`, {
                    end_time: new Date(),
                    status: 1
                })
            }
        });

        // 错误处理
        writeStream.on('error', (err) => {
            readStream.close()
            io.emit(`uploadFileStatus_${uuid}`, {
                uuid,
                end_time: new Date(),
                status: 2
            })
        });
        // 开始上传
        readStream.pipe(writeStream);
    }

    deleteFolderRecursive(connItem, folderPath) {
        return new Promise((resolve, reject) => {
            connItem.sftp.readdir(folderPath, (err, list) => {
                console.log(err)
                if (err) return reject(`读取文件夹 ${folderPath} 失败: ${err}`);
                const deletePromises = list.map(item => {
                    const fullPath = folderPath + '/' + item.filename
                    if (item.attrs.isDirectory()) {
                        // 递归删除子文件夹
                        return this.deleteFolderRecursive(connItem, fullPath).then(() => {
                            return new Promise((res, rej) => {
                                connItem.sftp.rmdir(fullPath, (err) => {
                                    console.log(err)
                                    if (err) rej(`删除子文件夹 ${fullPath} 失败: ${err}`);
                                    else res(`子文件夹 ${fullPath} 删除成功`);
                                });
                            });
                        });
                    } else {
                        return this.unlinkFile(connItem, fullPath);
                    }
                });

                Promise.all(deletePromises)
                    .then(results => {
                        console.log(results.join('\n'));
                        sftp.rmdir(folderPath + '/', (err) => {
                            if (err) reject(`删除文件夹 ${folderPath} 失败: ${err}`);
                            else resolve(`文件夹 ${folderPath} 删除成功`);
                        });
                    })
                    .catch(err => reject(err));
            });

        });
    };

    async unlinkFile(item, remoteFilePath) {
        remoteFilePath = remoteFilePath.replaceAll('//', '/')
        return new Promise((resolve, reject) => {
                item.sftp.unlink(remoteFilePath, (err) => {
                    if (err) {
                        console.log(err)
                        reject(err)
                    } else {
                        resolve()
                    }
                });
            }
        )
    }

    async unlinkFiles(args) {
        console.log(args)
        return new Promise((resolve, reject) => {
            let item = connList.find(el => el.data.uuid === args.uuid + '_sftp')
            if (!item) {
                reject('不存在该通道')
            } else {
                let p = Promise.all(args.pathList.map(el => {
                    if (el.is_folder === 1) {
                        return this.deleteFolderRecursive(item, el.path)
                    } else {
                        return this.unlinkFile(item, el.path)
                    }
                }))
                p.then(res => {
                    resolve()
                }).catch(e => {
                    reject(e)
                })
            }

        })


    }

    // 下载单个文件
    async downloadFile(sftp, remoteFile, localFile, args) {
        const {io} = Socket.getSocketServer();
        return new Promise((resolve, reject) => {
            let fileStream = createWriteStream(localFile);
            sftp.createReadStream(remoteFile)
                .on('data', (chunk) => {
                    fileStream.write(chunk);
                })
                .on('end', () => {
                    fileStream.end();
                    downloadLengthObj[args.uuid] = downloadLengthObj[args.uuid] + 1
                    io.emit(`downloadFilesProgress_${args.uuid}`, {
                        progress: ((downloadLengthObj[args.uuid] / totalLengthObj[args.uuid]) * 100).toFixed(2)
                    })
                    console.log(downloadLengthObj[args.uuid] === totalLengthObj[args.uuid])
                    if (downloadLengthObj[args.uuid] === totalLengthObj[args.uuid]) {
                        io.emit(`downloadFilesStatus_${args.uuid}`, {
                            status: 1,
                            end_time: new Date(),

                        })
                    }
                    resolve();
                })
                .on('error', (err) => {
                    console.log(err)
                    io.emit(`downloadFilesStatus_${args.uuid}`, {
                        end_time: new Date(),
                        status: 2
                    })
                    reject(`Error downloading file ${remoteFile}: ${err}`);
                });
        });
    }


    // 获取指定目录下的所有文件和文件夹（不递归）
    listFiles(sshClient, folderPath, args) {
        return new Promise((resolve, reject) => {
            sshClient.exec(`find "${folderPath}" -maxdepth 1 -exec stat --format="%s %n" {} +`, (err, stream) => {
                if (err) {
                    reject(err);
                    return;
                }

                let output = '';
                stream.on('data', (data) => {
                    output += data.toString();
                });

                stream.on('close', () => {
                    // 解析命令输出，提取文件的大小和路径
                    const files = output.split('\n')
                        .filter(line => line.trim() !== '')
                        .map(line => {
                            const [size, filePath] = line.split(' ', 2);
                            return {
                                size: parseInt(size, 10),
                                name: filePath.replace(folderPath, '').replace(/^\//, ''), // 去掉文件路径前面的 '/'
                                isDirectory: filePath.endsWith('/'),  // 判断文件路径是否是目录
                            };
                        });
                    totalLengthObj[args.uuid] = totalLengthObj[args.uuid] + files.length
                    resolve(files);
                });
            });
        });
    }

    getFileCount(sshClient, folderPath) {
        return new Promise((resolve, reject) => {
            // 使用 SSH 执行命令
            sshClient.exec(`find "${folderPath}" -type f | wc -l`, (err, stream) => {
                if (err) {
                    reject(err);
                    return;
                }

                let output = '';
                stream.on('data', (data) => {
                    output += data.toString();
                });

                stream.on('close', () => {
                    // 输出的结果是文件数量
                    const fileCount = parseInt(output.trim(), 10);
                    resolve(fileCount);
                });
            });
        });
    }

    // 递归下载文件夹
    async downloadDirectory(sftp, remoteDir, localDir, args) {
        return new Promise(((resolve, reject) => {
            const {io} = Socket.getSocketServer();

            if (!existsSync(localDir)) {
                mkdirSync(localDir)
            }
            try {
                sftp.readdir(remoteDir, async (err, list) => {
                    if (err) {
                        return;
                    }
                    console.log(list.map(el => el.filename))
                    for (let item of list) {

                        const remotePath = remoteDir + '/' + item.filename
                        const localFile = join(localDir, item.filename)

                        if (item.attrs.isDirectory()) {
                            await this.downloadDirectory(sftp, remotePath, localFile, args)
                        } else {
                            console.log(remotePath, downloadLengthObj[args.uuid], totalLengthObj[args.uuid])
                            this.downloadFile(sftp, remotePath, localFile, args)

                        }
                        resolve()
                    }
                });
            } catch (err) {
                reject(err)
                io.emit(`downloadFilesStatus_${args.uuid}`, {status: 2, end_time: new Date()})
            }
        }))

    }

    async downloadFileAndProgress(sftp, remoteFile, localFile, args) {
        const {io} = Socket.getSocketServer();
        return new Promise((resolve, reject) => {
            sftp.stat(remoteFile, (err, stats) => {
                if (!err) {
                    let size = stats.size
                    let download_size = 0
                    let fileStream = createWriteStream(localFile);
                    sftp.createReadStream(remoteFile)
                        .on('data', (chunk) => {
                            download_size += chunk.length
                            fileStream.write(chunk);
                            io.emit(`downloadFilesProgress_${args.uuid}`, {
                                progress: ((download_size / size) * 100).toFixed(2),
                                download_size,
                                size
                            })
                            if (size === download_size) {
                                io.emit(`downloadFilesStatus_${args.uuid}`, {
                                    status: 1,
                                    end_time: new Date(),

                                })
                            }

                        })
                        .on('end', () => {
                            fileStream.end();
                            io.emit(`downloadFilesStatus_${args.uuid}`, {
                                status: 1,
                                end_time: new Date(),

                            })
                            resolve();
                        })
                        .on('error', (err) => {
                            console.log(err)
                            io.emit(`downloadFilesStatus_${args.uuid}`, {
                                end_time: new Date(),
                                status: 2
                            })
                            reject(`Error downloading file ${remoteFile}: ${err}`);
                        });
                } else {
                    reject(err)
                }
            })

        });
    }

    async downloadFiles(args) {
        return new Promise(async (resolve, reject) => {
            let item = connList.find(el => el.data.uuid === args.sftp_uuid + '_sftp')
            if (!item) {
                reject('不存在该通道')
            } else {
                let paths = join(args.path, args.filename)
                downloadLengthObj[args.uuid] = 0
                totalLengthObj[args.uuid] = await this.getFileCount(item.conn, args.remote_path, args)

                resolve()
                if (args.is_folder === 1) {
                    await this.downloadDirectory(item.sftp, args.remote_path, paths, args)
                } else {
                    this.downloadFileAndProgress(item.sftp, args.remote_path, paths, args)
                }

            }

        })


    }

    exec(conn, cmd) {
        return new Promise((resolve, reject) => {
            conn.exec(cmd, (err, stream) => {
                if (err) {
                    console.log(err)
                    reject(err)
                } else {
                    stream.on('data', (data) => {
                        console.log(data)
                        resolve(data.toString())
                    });
                    stream.on('close', () => {
                        resolve();
                    });
                    // resolve(data.toString())
                }
            })
        })
    }

    async readImageToBase64(sftp, filePath) {
        return new Promise(async (resolve, reject) => {
                let chunks = [];

                sftp.createReadStream(filePath)
                    .on('data', r => {
                        chunks.push(r);
                    }).on('end', e => {
                    // 合并所有数据块
                    const imageBuffer = Buffer.concat(chunks);

                    // 将图片转换为Base64
                    const base64Image = imageBuffer.toString('base64');
                    const base64WithPrefix = `data:image/jpg;base64,${base64Image}`;
                    resolve(base64WithPrefix)
                }).on('error', e => {
                    reject(e)
                })
            }
        )

    }

    returnVideoStream(args) {
        return new Promise(((resolve, reject1) => {
            let item = connList.find(el => el.data.uuid === args.sftp_uuid + '_sftp')
            // 设置响应头，以便浏览器正确处理视频流
            this.app.response.set({
                'Content-Type': 'application/octet-stream',
                'Content-Disposition': 'inline; filename="video.mp4"'
            });
            // 通过 SFTP 创建读取流
            const steam = item.sftp.createReadStream(args.filePath)
            steam.pipe(this.app.response.res)
        }))

    }

    async saveFileText(args) {
        return new Promise((resolve, reject) => {
            let item = connList.find(el => el.data.uuid === args.sftp_uuid + '_sftp')
            // 使用 sftp.writeFile 写入文件
            item.sftp.writeFile(args.filePath, args.data, (err) => {
                if (err) {
                    reject(err)
                    console.log(err)
                } else {
                    resolve()
                }
            });
        })

    }

    async updateFileName(args) {
        let {oldFilePath, newFilePath, sftp_uuid} = args
        oldFilePath = oldFilePath.replaceAll('//', '/')
        newFilePath = newFilePath.replaceAll('//', '/')
        return new Promise(async (resolve, reject) => {
            let item = connList.find(el => el.data.uuid === sftp_uuid + '_sftp')
            try {
                console.log(`mv ${oldFilePath} ${newFilePath}`)
                await this.exec(item.conn, `mv ${oldFilePath} ${newFilePath}`)
                resolve()
            } catch (e) {
                console.log(e)
                reject(e)
            }
        })

    }

    async previewFile(args) {
        args.filePath = args.filePath.replaceAll('//', '/')
        return new Promise(async (resolve, reject) => {
                const item = connList.find(el => el.data.uuid === args.sftp_uuid + `_sftp`)
                if (!item) {
                    reject()
                } else {
                    try {
                        let text, type
                        type = await this.exec(item.conn, `file ${args.filePath}`)
                        if (type.includes('text')) {
                            text = await this.exec(item.conn, `cat ${args.filePath}`)
                        } else if (type.includes('image')) {
                            text = await this.readImageToBase64(item.sftp, args.filePath)
                        }
                        resolve({text, type})
                    } catch (e) {
                        console.log(e)
                        reject(e)
                    }

                }
            }
        )

    }

    async connSsh(args) {
        return new Promise(function (resolve, reject) {
            try {
                const conn = new Client()
                conn.connect(args);
                conn.on('ready', () => {
                    resolve(json({}))
                });
                conn.on('error', (err) => {
                    console.error('SSH连接错误:', err);
                    resolve(json({code: 500, msg: err.message}))
                });

            } catch (e) {
                reject(json({code: 500, msg: e.message}))
            }

        })
    }
}

module
    .exports = SshService;